package main

import (
	"crypto/sha1"
	"encoding/json"
	"errors"
	"fmt"
	"github.com/go-redis/redis/v7"
	"github.com/mattheath/base62"
	"io"
	"time"
)

const (
	// URLIDKEY is global counter
	URLIDKEY = "next.url.id"
	// ShortlinkKey mapping ths shortlink to the url
	ShortlinkKey = "shortlink:%s:url"
	// URLHashKey mapping ths Hash of the url to the shortlink
	URLHashKey = "urlhash:%s:url"
	// ShortlinkDetailKey mapping the shortlink to the detail of url
	ShortlinkDetailKey = "shortlink:%s:detail"
)

type RedisCli struct {
	Cli *redis.Client
}

type URLDetail struct {
	URL                 string        `json:"url"`
	CreateAt            string        `json:"create_at"`
	ExpirationInMinutes time.Duration `json:"expiration_in_minutes"`
}

func NewRedisCli(addr string, passwd string, db int) *RedisCli {
	c := redis.NewClient(&redis.Options{
		Addr:     addr,
		Password: passwd, // no password set
		DB:       db,     // use default DB
	})
	_, err := c.Ping().Result()
	if err != nil {
		panic(err)
	}
	return &RedisCli{
		Cli: c,
	}
}

func (r *RedisCli) Shorten(url string, exp int64) (string, error) {
	h := toSha1(url)
	d, err := r.Cli.Get(fmt.Sprintf(URLHashKey, h)).Result()
	if err == redis.Nil {
		fmt.Sprintf("%s does not exist", fmt.Sprintf(URLHashKey, h))
		// not existed, nothing to do
	} else if err != nil {
		return "", err
	} else {
		if d == "{}"{
			// expiration, nothing to do
		} else {
			return d, nil
		}
	}

	err = r.Cli.Incr(URLIDKEY).Err()
	if err != nil {
		return "", err
	}

	id, err := r.Cli.Get(URLIDKEY).Int64()
	if err != nil {
		return "", err
	}
	eid := base62.EncodeInt64(id)
	err = r.Cli.Set(fmt.Sprintf(ShortlinkKey, eid), url, time.Minute*time.Duration(exp)).Err()
	if err != nil {
		return "", err
	}

	err = r.Cli.Set(fmt.Sprintf(URLHashKey, h), eid, time.Minute*time.Duration(exp)).Err()
	if err != nil {
		return "", err
	}

	detail, err := json.Marshal(&URLDetail{
		URL: url,
		CreateAt: time.Now().String(),
		ExpirationInMinutes: time.Duration(exp),
	})
	if err != nil {
		return "", err
	}
	err = r.Cli.Set(fmt.Sprintf(ShortlinkDetailKey, eid), detail, time.Minute*time.Duration(exp)).Err()
	if err != nil {
		return "", err
	}
	return eid, nil
}

func toSha1(url string) string {
	t := sha1.New()
	io.WriteString(t, url)
	return fmt.Sprintf("%x", t.Sum(nil))
}
// ShortlinkInfo returns the detail of the shortlink
func (r *RedisCli)ShortlinkInfo(eid string)(interface{}, error)  {
	d, err :=r.Cli.Get(fmt.Sprintf(ShortlinkDetailKey, eid)).Result()
	if err == redis.Nil {
		return "", StatusError{404, errors.New("Unknow short url")}
	} else if err != nil {
		return "", err
	} else {
		var detail URLDetail
		if err := json.Unmarshal([]byte(d), &detail); err == nil {
			return detail,nil
		} else {
			fmt.Println(err)
			return "", err
		}
	}
}

// Unshorten conver shortlink to url
func (r *RedisCli)Unshorten(eid string)(string,error)  {
	url, err :=r.Cli.Get(fmt.Sprintf(ShortlinkKey, eid)).Result()
	if err == redis.Nil {
		return "", StatusError{404, err}
	} else if err != nil {
		return "", err
	} else {
		return url,nil
	}
}